package org.juiceshop.groovy.orientdb;


import groovy.lang.Closure;
import groovy.lang.GString;

import java.util.List;
import java.util.Map;

import org.codehaus.groovy.runtime.DefaultGroovyMethods;

import com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.metadata.schema.OSchema;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.OCommandSQL;
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery;

public class Database extends ODatabaseDocumentTx {
	
	public Database(String iURL) {
		super(iURL);
	}
	
	public boolean isLocal() {
		return getURL().startsWith("local:");
	}
	
	public boolean isRemote() {
		return getURL().startsWith("remote:");
	}

	public boolean isMemory() {
		return getURL().startsWith("memory:");
	}

	public void create (Closure<Void> closure) {
		create();
		connect(closure);
	}

	public void connect (String user, String password, Closure<Void> closure) {
		if (this.isClosed()) {
			open(user, password);			
		}
		try {
			DefaultGroovyMethods.use(this, OrientDbExtensions.class, closure);
		} finally {
			if (!isMemory()) close();
		}
	}
		
	public void connect (Closure<Void> closure) {
		connect("admin","admin", closure);
	}

	public List<ODocument> querySQL(String sql, Object...args) {
		List<ODocument> result;
		if (args.length==0) {
		    OSQLSynchQuery<ODocument> query = new OSQLSynchQuery<>(sql);
			result = query(query);
		} else {
			result = prepare(sql).execute(args);
		}
		return result;
	}

	public List<ODocument> querySQL(Map<String,Object> args, String sql) {
		OCommandRequest req = prepare(sql);
		return req.execute(args);
	}

	private String gStringSql(GString sql) {
		StringBuilder bldr = new StringBuilder();
		int valueCount = sql.getValueCount();
		String[] strings = sql.getStrings();
		for (int i=0; i<valueCount; i++) {
			bldr.append(strings[i]).append('?');
		}
		bldr.append(strings[valueCount]);
		return bldr.toString();
	}
	
	public List<ODocument> querySQL(GString sql) {
		return querySQL(gStringSql(sql),sql.getValues());
	}
	
	public OCommandRequest prepare (String sql) {
		OSQLSynchQuery<ODocument> query = new OSQLSynchQuery<>(sql);
		return command(query);
	}
	
	
	public Object commandSQL(String sql) {
		return command(new OCommandSQL(sql)).execute();
	}
	
	public Object commandSQL(String sql, Object...args) {
		return command(new OCommandSQL(sql)).execute(args);
	}

	public Object commandSQL(GString sql) {
		return command(new OCommandSQL(gStringSql(sql))).execute(sql.getValues());
	}

	/**
	 * Creates a Table instance for a document class.
	 * @param className Name of a document class.
	 * @return a new Table instance.
	 */
	public Table table (String className) {
		return new Table(this,className,null);
	}	
	
	/**
	 * Creates a Table instance for a given document class and an index key field.
	 * The index will be opened at first access, so this method may be called when database is closed.
	 * @param className Name of a document class.
	 * @param indexField Name of an indexed field.
	 * @return
	 */
	public Table table (String className, String indexField) {
		return new Table(this,className,indexField);
	}	
	
	/**
	 * Executes a closure in the context of a transaction.
	 * Begins a transaction and executes the given closure. If an exception occurs, transaction will be rolled back and
	 * the exception will be rethrown. Otherwise the transaction will be committed.
	 * @param closure Closure without parameters.
	 */
	public void inTransaction(Closure<Void> closure) {
		try {
			begin();
			closure.call();
			commit();
		} catch (Exception x) {
			rollback();
			throw x;
		}
	}

	public OIndex<?> index(String indexName) {
		OIndex<?> oindex = getMetadata().getIndexManager().getIndex(indexName);
		if (oindex==null) {
			throw new IllegalArgumentException("Index not defined: "+indexName);
		}
		return oindex;
	}
	
	
	public OSchema schema(Closure<Void> closure) {
		return Utils.callClosure(closure,getMetadata().getSchema());
	}
	
}
